/*************************************************************
 * Title: JsonDataFormatter.java
 * Description: 
 * Author: Huang Shaobin
 * Email: huangshaobin@qtt.cn
 * CreateTime: Feb 11, 2013 3:06:50 PM
 * Copyright © 北京全天通信息咨询服务有限公司 All right reserved
 ************************************************************/
package snow.core.toolkit.format;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.util.ClassUtils;

import snow.core.exception.ApplicationException;
import snow.core.toolkit.ClassHelper;
import snow.core.toolkit.DateHelper;

/**
 * JSON格式化工具
 */
public class JsonDataFormatter implements DataFormatter {

	public static String NULL_OBJECT_JSON = "{}";
	public static String EMPTY_ARRAY_JSON = "[]";
	public static String EMPTY_MAP_JSON = NULL_OBJECT_JSON;
	public static String EMPTY_COLLECTION_JSON = EMPTY_ARRAY_JSON;
	public static String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";

	private final Log log = LogFactory.getLog(getClass());

	private final List<Class<?>> toStringClassSet;
	private Set<Class<?>> ignorePropertyTypeSet;

	public JsonDataFormatter() {
		Class<?>[] classes = { CharSequence.class, Number.class, Locale.class, Enum.class };
		toStringClassSet = new ArrayList<Class<?>>(classes.length);
		for (Class<?> clazz : classes) {
			toStringClassSet.add(clazz);
		}
	}

	public String format(Object object) throws ApplicationException {
		if (object == null) {
			return NULL_OBJECT_JSON;
		}
		if (isNativeObject(object)) {
			return String.valueOf(object);
		}
		if (object instanceof Date) {
			return DateHelper.format((Date) object, DEFAULT_DATE_FORMAT);
		}
		if (object instanceof Object[]) {
			return toJsonFrom((Object[]) object).toString();
		}
		if (object instanceof boolean[]) {
			return toJsonFrom((boolean[]) object).toString();
		}
		if (object instanceof byte[]) {
			return toJsonFrom((byte[]) object).toString();
		}
		if (object instanceof char[]) {
			return toJsonFrom((char[]) object).toString();
		}
		if (object instanceof short[]) {
			return toJsonFrom((short[]) object).toString();
		}
		if (object instanceof int[]) {
			return toJsonFrom((int[]) object).toString();
		}
		if (object instanceof long[]) {
			return toJsonFrom((long[]) object).toString();
		}
		if (object instanceof float[]) {
			return toJsonFrom((float[]) object).toString();
		}
		if (object instanceof double[]) {
			return toJsonFrom((double[]) object).toString();
		}
		if (object instanceof Collection) {
			return toJsonFrom((Collection<?>) object).toString();
		}
		if (object instanceof Map) {
			return toJsonFrom((Map<?, ?>) object).toString();
		}
		return toJsonFrom(object).toString();
	}

	private Formatter getFormatterFrom(Method method, boolean isDynamicProxy) {
		if (method != null) {
			if (isDynamicProxy) {
				Class<?> superClass = method.getDeclaringClass().getSuperclass();
				if (superClass != null) {
					Method targetMethod = BeanUtils.findDeclaredMethod(superClass, method.getName(), method.getParameterTypes());
					return getFormatterFrom(targetMethod, ClassHelper.isDynamicProxy(superClass));
				}
			}
			return method.getAnnotation(Formatter.class);
		}
		return null;
	}

	public Set<String> getIgnorePropertyTypeSet() {
		Set<String> ignorePropertyTypeSet = Collections.emptySet();
		if (CollectionUtils.isNotEmpty(this.ignorePropertyTypeSet)) {
			ignorePropertyTypeSet = new HashSet<String>(this.ignorePropertyTypeSet.size());
			for (Class<?> ignorePropertyType : this.ignorePropertyTypeSet) {
				if (ignorePropertyType != null) {
					ignorePropertyTypeSet.add(ignorePropertyType.getName());
				}
			}
		}
		return ignorePropertyTypeSet;
	}

	private boolean isNativeClass(Class<?> clazz) {
		if (ClassUtils.isPrimitiveWrapper(clazz)) {
			return true;
		}
		for (Class<?> superClass : toStringClassSet) {
			if (superClass.isAssignableFrom(clazz)) {
				return true;
			}
		}
		return false;
	}

	private boolean isNativeObject(Object value) {
		return isNativeClass(value.getClass());
	}

	/**
	 * @param propertyType
	 * @return
	 */
	private boolean needSerializeFrom(Class<?> propertyType) {
		if (propertyType != null) {
			if (CollectionUtils.isNotEmpty(this.ignorePropertyTypeSet)) {
				for (Class<?> ignorePropertyType : this.ignorePropertyTypeSet) {
					if (ignorePropertyType != null && ignorePropertyType.isAssignableFrom(propertyType)) {
						return false;
					}
				}
			}
			return true;
		}
		return false;
	}

	public void setIgnorePropertyTypeSet(Set<String> ignorePropertyTypeSet) {
		if (CollectionUtils.isNotEmpty(ignorePropertyTypeSet)) {
			this.ignorePropertyTypeSet = new HashSet<Class<?>>(ignorePropertyTypeSet.size());
			for (String ignorePropertyTypeName : ignorePropertyTypeSet) {
				try {
					Class<?> ignorePropertyType = Class.forName(ignorePropertyTypeName);
					if (ignorePropertyType != null) {
						this.ignorePropertyTypeSet.add(ignorePropertyType);
					}
				} catch (Exception e) {
					log.warn(e.getMessage(), e);
				}
			}
		}
	}

	public JSONArray toJsonFrom(boolean[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (boolean value : array) {
				json.put(value);
			}
		}
		return json;
	}

	public JSONArray toJsonFrom(byte[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (byte value : array) {
				json.put(value);
			}
		}
		return json;
	}

	public JSONArray toJsonFrom(char[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (char value : array) {
				json.put(value);
			}
		}
		return json;
	}

	/**
	 * @param object
	 * @return
	 */
	public JSONArray toJsonFrom(Collection<?> collection) {
		final JSONArray json = new JSONArray();
		if (CollectionUtils.isNotEmpty(collection)) {
			for (Object item : collection) {
				if (item == null) {
					json.put(NULL_OBJECT_JSON);
				} else if (isNativeObject(item)) {
					json.put(item);
				} else if (item instanceof Date) {
					json.put(DateHelper.format((Date) item, DEFAULT_DATE_FORMAT));
				} else if (item instanceof boolean[]) {
					json.put(toJsonFrom((boolean[]) item));
				} else if (item instanceof byte[]) {
					json.put(toJsonFrom((byte[]) item));
				} else if (item instanceof char[]) {
					json.put(toJsonFrom((char[]) item));
				} else if (item instanceof double[]) {
					json.put(toJsonFrom((double[]) item));
				} else if (item instanceof float[]) {
					json.put(toJsonFrom((float[]) item));
				} else if (item instanceof int[]) {
					json.put(toJsonFrom((int[]) item));
				} else if (item instanceof long[]) {
					json.put(toJsonFrom((long[]) item));
				} else if (item instanceof short[]) {
					json.put(toJsonFrom((short[]) item));
				} else if (item instanceof Object[]) {
					json.put(toJsonFrom((Object[]) item));
				} else if (item instanceof Collection) {
					json.put(toJsonFrom((Collection<?>) item));
				} else if (item instanceof Map) {
					json.put(toJsonFrom((Map<?, ?>) item));
				} else {
					json.put(toJsonFrom(item));
				}
			}
		}
		return json;
	}

	public JSONArray toJsonFrom(double[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (double value : array) {
				json.put(JSONObject.doubleToString(value));
			}
		}
		return json;
	}

	public JSONArray toJsonFrom(float[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (float value : array) {
				json.put(JSONObject.doubleToString(value));
			}
		}
		return json;
	}

	public JSONArray toJsonFrom(int[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (int value : array) {
				json.put(value);
			}
		}
		return json;
	}

	public JSONArray toJsonFrom(long[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (long value : array) {
				json.put(value);
			}
		}
		return json;
	}

	/**
	 * @param object
	 * @return
	 */
	public JSONObject toJsonFrom(Map<?, ?> map) {
		final JSONObject json = new JSONObject();
		if (MapUtils.isNotEmpty(map)) {
			Set<?> entrySet = map.entrySet();
			for (Object item : entrySet) {
				try {
					Map.Entry<?, ?> entry = (Map.Entry<?, ?>) item;
					String key = String.valueOf(entry.getKey());
					Object value = entry.getValue();
					if (value == null) {
						json.put(key, NULL_OBJECT_JSON);
					} else if (isNativeObject(value)) {
						json.put(key, value);
					} else if (value instanceof Date) {
						json.put(key, DateHelper.format((Date) value, DEFAULT_DATE_FORMAT));
					} else if (value instanceof boolean[]) {
						json.put(key, toJsonFrom((boolean[]) value));
					} else if (value instanceof byte[]) {
						json.put(key, toJsonFrom((byte[]) value));
					} else if (value instanceof char[]) {
						json.put(key, toJsonFrom((char[]) value));
					} else if (value instanceof double[]) {
						json.put(key, toJsonFrom((double[]) value));
					} else if (value instanceof float[]) {
						json.put(key, toJsonFrom((float[]) value));
					} else if (value instanceof int[]) {
						json.put(key, toJsonFrom((int[]) value));
					} else if (value instanceof long[]) {
						json.put(key, toJsonFrom((long[]) value));
					} else if (value instanceof short[]) {
						json.put(key, toJsonFrom((short[]) value));
					} else if (value instanceof Object[]) {
						json.put(key, toJsonFrom((Object[]) value));
					} else if (value instanceof Collection) {
						json.put(key, toJsonFrom((Collection<?>) value));
					} else if (value instanceof Map) {
						json.put(key, toJsonFrom((Map<?, ?>) value));
					} else {
						json.put(key, toJsonFrom(value));
					}
				} catch (JSONException e) {
					log.warn(e.getMessage(), e);
				}
			}
		}
		return json;
	}

	private JSONObject toJsonFrom(Object bean) {
		final Class<?> beanClass = bean.getClass();
		boolean isDynamicProxy = ClassHelper.isDynamicProxy(beanClass); // 判断是否为动态代理
		final JSONObject json = new JSONObject();
		PropertyDescriptor[] descriptors = null;
		descriptors = BeanUtils.getPropertyDescriptors(beanClass);
		for (PropertyDescriptor descriptor : descriptors) {
			Method readMethod = descriptor.getReadMethod();
			Method writeMethod = descriptor.getWriteMethod();
			Class<?> propertyType = descriptor.getPropertyType();
			if (readMethod != null && writeMethod != null && needSerializeFrom(propertyType)) {
				final Formatter formatter = getFormatterFrom(readMethod, isDynamicProxy);
				if (formatter != null && !formatter.serialize()) {
					continue;
				}
				final String key = formatter != null && StringUtils.isNotBlank(formatter.name()) ? formatter.name() : descriptor.getName();
				Object value = null;
				try {
					readMethod.setAccessible(true); // 强制设置为可访问，以保证可以正确输出信息
					value = readMethod.invoke(bean, ArrayUtils.EMPTY_OBJECT_ARRAY);
				} catch (Exception e) {
					e.printStackTrace();
					log.warn(e.getMessage(), e);
				}
				if (value == null) {
					if (isNativeClass(propertyType)) {
						value = StringUtils.EMPTY;
					} else {
						value = NULL_OBJECT_JSON;
					}
				} else if (isNativeObject(value)) {

				} else if (value instanceof Date) {
					value = DateHelper.format((Date) value, formatter != null && StringUtils.isNotBlank(formatter.format()) ? formatter.format()
							: DEFAULT_DATE_FORMAT);
				} else if (value instanceof boolean[]) {
					value = toJsonFrom((boolean[]) value);
				} else if (value instanceof byte[]) {
					value = toJsonFrom((byte[]) value);
				} else if (value instanceof char[]) {
					value = toJsonFrom((char[]) value);
				} else if (value instanceof double[]) {
					value = toJsonFrom((double[]) value);
				} else if (value instanceof float[]) {
					value = toJsonFrom((float[]) value);
				} else if (value instanceof int[]) {
					value = toJsonFrom((int[]) value);
				} else if (value instanceof long[]) {
					value = toJsonFrom((long[]) value);
				} else if (value instanceof short[]) {
					value = toJsonFrom((short[]) value);
				} else if (value instanceof Object[]) {
					value = toJsonFrom((Object[]) value);
				} else if (value instanceof Collection) {
					value = toJsonFrom((Collection<?>) value);
				} else if (value instanceof Map) {
					value = toJsonFrom((Map<?, ?>) value);
				} else {
					value = toJsonFrom(value);
				}
				try {
					json.put(key, value);
				} catch (JSONException e) {
					log.warn(e.getMessage(), e);
				}
			}
		}
		return json;
	}

	public JSONArray toJsonFrom(Object[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (Object value : array) {
				if (value == null) {
					json.put(NULL_OBJECT_JSON);
				} else if (isNativeObject(value)) {
					json.put(value);
				} else if (value instanceof Date) {
					json.put(DateHelper.format((Date) value, DEFAULT_DATE_FORMAT));
				} else if (value instanceof boolean[]) {
					json.put(toJsonFrom((boolean[]) value));
				} else if (value instanceof byte[]) {
					json.put(toJsonFrom((byte[]) value));
				} else if (value instanceof char[]) {
					json.put(toJsonFrom((char[]) value));
				} else if (value instanceof double[]) {
					json.put(toJsonFrom((double[]) value));
				} else if (value instanceof float[]) {
					json.put(toJsonFrom((float[]) value));
				} else if (value instanceof int[]) {
					json.put(toJsonFrom((int[]) value));
				} else if (value instanceof long[]) {
					json.put(toJsonFrom((long[]) value));
				} else if (value instanceof short[]) {
					json.put(toJsonFrom((short[]) value));
				} else if (value instanceof Object[]) {
					json.put(toJsonFrom((Object[]) value));
				} else if (value instanceof Collection) {
					json.put(toJsonFrom((Collection<?>) value));
				} else if (value instanceof Map) {
					json.put(toJsonFrom((Map<?, ?>) value));
				} else {
					json.put(toJsonFrom(value));
				}
			}
		}
		return json;
	}

	public JSONArray toJsonFrom(short[] array) {
		final JSONArray json = new JSONArray();
		if (!ArrayUtils.isEmpty(array)) {
			for (short value : array) {
				json.put(value);
			}
		}
		return json;
	}

}
